var _gl = require('opengl');
var _textures = require('game/texture.js');
var _program = require('global/programs.js');
var _Context = require('render/drawcontext.js');
var _Camera = require('render/camera.js');
var _UpdateContext = require('render/updatecontext.js');
var _TouchContext = require('render/eventcontext.js');

var _Sprite = require('drawable/spritenode.js');
var _9Patch = require('drawable/ninepatch.js');
var _Color = require('drawable/colornode.js');
var _FontSuit = require('core/fontsuit.js');

var _NamedList = require('core/namedlist.js');

// ==============================================
// Camera and ScreenAdapter
// ==============================================
var mCamera = _Camera.createCamera().lookAt([0, 0, 10], [0, 0, 0], [0, 1, 0]).ortho(0, 1, 0, 1, 9, 11);
var mRenderContext = new _Context(mCamera);
exports.mCamera = mCamera;
exports.mRenderContext = mRenderContext;
exports.mScreenAdapter = new (require('render/screenadapter.js').FixHeightAdapter)(mCamera, 480);

// ==============================================
// Sprite create utils
// ==============================================
/**
 * hold all dependencies for creating a sprite like node
 * @constructor
 */
function Sprite(material, frame) {
    this.material = material;
    this.frame = frame;
}
Sprite.prototype.sprite = function () {
    return new _Sprite(this.material, this.frame);
};
Sprite.prototype.$9patch = function () {
    return _9Patch.create9Patch.apply(this, [this.material, this.frame].concat(Array.prototype.slice.call(arguments, 0)));
};
Sprite.prototype.$9patch_v = function () {
    return _9Patch.create9Patch_v.apply(this, [this.material, this.frame].concat(Array.prototype.slice.call(arguments, 0)));
};
Sprite.prototype.$9patch_h = function () {
    return _9Patch.create9Patch_h.apply(this, [this.material, this.frame].concat(Array.prototype.slice.call(arguments, 0)));
};
function sprite(id) {
    var f = _textures.createFrame(id);
    return new Sprite(_program.positionTexture.material(f), f);
}
exports.sprite = sprite;

/**
 * create node
 *
 * @param id
 * @returns {_Sprite}
 */
function spriteNode(id) {
    var frame = _textures.createFrame(id);
    return new _Sprite(_program.positionTexture.material(frame), frame);
}
function spriteFrame(id) {
    return _textures.createFrame(id);
}
function colorNode(color, w, h) {
    return new _Color(_program.positionColor.material(color), w, h);
}
exports.spriteNode = spriteNode;
exports.colorNode = colorNode;
exports.spriteFrame = spriteFrame;
exports.material = {
    positionTexture: function (frame) {
        return _program.positionTexture.material(frame);
    }
};

// ==============================================
// Task
// ==============================================
var mTasks = [];
function Task(fn, bind) {
    this.fn = fn;
    this.bind = bind || this;
}
Task.prototype.run = function () {
    this.fn.call(this.bind);
};
exports.scheduleTask = function (fn, bind) {
    mTasks.push(new Task(fn, bind));
};

// ==============================================
// Fonts and Text
// ==============================================
var _Text = require('drawable/text.js');
var _StyleText = require('drawable/styletext.js');

_FontSuit.prototype.textNode = function (font, size, text) {
    var pText = new _Text(_program.textBlack.material(this.mAtlas), this.findFont(font, size));
    if (text) {
        pText.setText(text);
    }
    return pText;
};
_FontSuit.prototype.colorTextNode = function (font, size, text, color) {
    var pText = new _Text(_program.textColor.material(this.mAtlas, color), this.findFont(font, size));
    if (text) {
        pText.setText(text);
    }
    return pText;
};
_FontSuit.prototype.styleText = function (font) {
    return new _StyleText(_program.textStyled.material(this.mAtlas), font);
};

function FontSuits() {
    this.mFontSuits = {};
}
FontSuits.prototype.findSuit = function (name, width, height) {
    var suit = this.mFontSuits[name];
    if (!suit) {
        suit = this.mFontSuits[name] = new _FontSuit(width || 512, height || 512, 'fonts/');
    }
    return suit;
};
FontSuits.prototype.removeSuit = function (name) {
    delete this.mFontSuits[name];
};

var fontSuits = new FontSuits();
var basicFont = fontSuits.findSuit('basic', 512, 512);
exports.findFontSuit = function (name, width, height) {
    return fontSuits.findSuit(name, width, height);
};
exports.textNode = function (font, size, text) {
    return basicFont.textNode(font, size, text);
};
exports.colorTextNode = function (font, size, text, color) {
    return basicFont.colorTextNode(font, size, text, color);
};
exports.removeFontSuit = function (name) {
    return fontSuits.removeSuit(name);
};

// ==============================================
// Schedule
// ==============================================
function Schedule(namedlist) {
    this._coll = namedlist;
    this._itor = namedlist.iterator();
}
Schedule.prototype.schedule = function (obj) {
    this._coll.add(obj);
};
Schedule.prototype.cancel = function (obj) {
    this._coll.remove(obj);
};
Schedule.prototype.iterator = function () {
    this._itor.reset();
    return this._itor;
};

var scheduleRender = new Schedule(new _NamedList('__render__'));
var scheduleUpdate = new Schedule(new _NamedList('__update__'));
var scheduleEvent = new Schedule(new _NamedList('__event__'));
var sceneCollection = new Schedule(new _NamedList('__scene__'));

var updateContext = new _UpdateContext(scheduleUpdate.iterator());
var touchContext = new _TouchContext();

exports.scheduleRender = scheduleRender;
exports.scheduleUpdate = scheduleUpdate;
exports.scheduleEvent = scheduleEvent;
exports.updateContext = updateContext;
exports.scenes = sceneCollection;

exports.findScene = function (id) {
    var itor = sceneCollection.iterator();
    while (itor.hasNext()) {
        var scene = itor.next();
        if (scene.mId == id) {
            return scene;
        }
    }
};
exports.registerScene = function (id, scene) {
    scene.mId = id;
    scene.onRegister(exports);
    sceneCollection.schedule(scene);
};
exports.unregisterScene = function (scene) {
    if (typeof scene == 'string') {
        scene = exports.findScene(scene);
        if (!scene) {
            return;
        }
    }

    scheduleRender.cancel(scene);
    scheduleUpdate.cancel(scene);
    scheduleEvent.cancel(scene);
    sceneCollection.cancel(scene);
};
exports.onScreenSizeChange = function (swidth, sheight) {
    exports.mScreenAdapter.onSizeChange(swidth, sheight);
    mRenderContext.onChange();

    var width = exports.mScreenAdapter.mWidth;
    var height = exports.mScreenAdapter.mHeight;
    var itor = sceneCollection.iterator();
    while (itor.hasNext()) {
        itor.next().onSizeChange(width, height);
    }
};

/**
 * update and draw all elements
 */
exports.runSchedule = function () {
    updateContext.ticktack();
    var itor;

    // touch
    touchContext.startTouch();
    itor = scheduleEvent.iterator();
    while (itor.hasNext()) {
        try {
            touchContext.onEvent(mCamera, itor.next());
        } catch (e) {
            console.error('touch.exception:' + e);
        }
    }
    touchContext.endTouch();

    // update
    itor = scheduleUpdate.iterator();
    while (itor.hasNext()) {
        itor.next().update(updateContext);
    }

    // drawing
    _gl.clear(_gl.COLOR_BUFFER_BIT);
    itor = scheduleRender.iterator();
    while (itor.hasNext()) {
        itor.next().draw(mRenderContext);
    }

    // run task;
    while (mTasks.length > 0) {
        mTasks.shift().run();
    }
};

/**
 * render all elements
 */
exports.renderSchedule = function () {
    // drawing
    _gl.clear(_gl.COLOR_BUFFER_BIT);
    var itor = scheduleRender.iterator();
    while (itor.hasNext()) {
        itor.next().draw(mRenderContext);
    }

    // run task;
    while (mTasks.length > 0) {
        mTasks.shift().run();
    }
};
